Dplyr is a grammar for data manipulation. It combines various steps of manipulation in one chunk of code without nesting.
For more information check the official webside.
Tidyverse
The package is part of the “tidyvers-Universe”. Tidyverse is a a collection of packages which share a common philosophy of data analysis and R programming and are designed to work together seamlessly.
- ggplot2, for data visualisation.
- dplyr, for data manipulation.
- tidyr, for data tidying.
- readr, for data import.
- purrr, for functional programming.
- tibble, for tibbles, a modern re-imagining of data frames.
They share common data representation and API design and the tidyverse package allows you to install an update them as a set (they update frequently).
Introduction
This tutorial will introduce the basic commands to work with dplyr. This cheat sheetby R-Studio gives a overview of more helpful commands.
The functions work in a “regular” way and in the %>%-version.
or
As the author of this tutorial likes the %>% a lot, most functions will be shown in this way. The shortcut for ‘%>%’ is strg+command+m
Let’s get started. First you have to install and load the the tidyverse package (or dplyr and ggplot2 if you want to do it separatly).
library(tidyverse)
#we use the diamonds-data
select()
selects a set of columns
#Classic Style
select(diamonds, cut, color)
#pipe-style
diamonds %>% select(cut,color)
#adding the comand head at the end, to only select the fist five rows
diamonds %>% select(cut, color) %>% head
#select all rows staring with "c"
diamonds %>% select(starts_with('c'),price)
#Also works with ends_with(), contains(), matches(),...
filter
You can filter by the values of one or more columns.
#filter by one column
diamonds %>% filter(color=='E')
diamonds %>% filter(price>350)
#the %in%-command is usefull to filter on several values
diamonds %>% filter(color %in% c('E','F'))
#The ! in front of the command changes the operator to "not in"
diamonds %>% filter(!color %in% c('E','F'))
#filter on more than one column
#The "AND"-combination can be done in two ways
diamonds %>% filter(color=='E' , price>350)
#diamonds %>% filter(color=='E' & price>350)
#has the same output
#filtering with "OR" on two columns
diamonds %>% filter(color=='E' | price>350)
arrange
reorder the rows by one or more columns
#reorder by the column "cut"
diamonds %>% arrange(cut)
#reorder by the column cut and price. To order in descending way, put the column in desc()
diamonds %>% arrange(cut,desc(price))
mutate
you can create new columns via mutate
#simple new column
diamonds %>% select(cut, color, price) %>%
mutate(price_n = price*1000)
#new column base on a case-statement
diamonds %>% select(cut, color, price) %>%
mutate(cut_n = case_when(.$cut=='Ideal' ~ '1',
.$cut=='Premium' ~ '2',
.$cut=='Very Good' ~ '3',
.$cut=='Good' ~ '4',
.$cut=='Fair' ~ '5',
TRUE ~ '9'))
#if you miss a value, it is going to be NA
# the TRUE-statement ist kind of like a "else"-statement
summarise
You can use all kind of functions to summarize the data (sum, mean, median, …..).
#calculating the mean of the column price
diamonds %>% summarise(mean(price))
#adding the number distinct values of the column cut and the number of rows in the dataframe
diamonds %>% summarise(mean_price=mean(price),
num_cut=n_distinct(cut),
num_row=n())
group_by
group_by builds groups by the defined columns and does the operations within those groups. For those familiar with sql it works pretty much the same as “group by”
#Group by one column and then summarising with those groups
diamonds %>% group_by(cut) %>% summarise(mean_price=mean(price))
piping comands together
So fare the commands mostly just looked different to “regular” r-commands. Maybe are more straight forward. But to experience (IMO) the real advantage of the dplyr-package, you have to start piping the commands.
# Select all diamonds with a fair cut and then calucate the mean price depending on the clarity
diamonds %>% filter(cut=='Fair') %>% group_by(clarity) %>% summarise(mean_price=mean(price))
some more helpfull functions
cast / melt -> functions of the reshaping2 package
If you don’t know the functions, check them out. They really help to reorganize data.
in this context (tidyvers-universe) they are called spread and gather and are part of the package “tidyr”
#spread the result
diamonds %>%
filter(cut!='Fair') %>%
group_by(cut,clarity) %>%
summarise(mean_price=mean(price)) %>%
spread(key=cut,value=mean_price)
#reversing the spread with gather
diamonds %>%
filter(cut!='Fair') %>%
group_by(cut,clarity) %>%
summarise(mean_price=mean(price)) %>%
spread(key=cut,value=mean_price) %>%
gather(cut,price, Good:Ideal )
show the entire data.frame
dplyr always shows the first 10 rows, but sometimes you just want to see them all
#to see the result in the console, use print(n=nrow(.))
diamonds %>%
filter(cut!='Fair') %>%
group_by(cut,clarity) %>%
summarise(mean_price=mean(price)) %>%
spread(key=cut,value=mean_price) %>%
gather(cut,price, Good:Ideal ) %>%
print(n = nrow(.))
# A tibble: 32 × 3
clarity cut price
<ord> <chr> <dbl>
1 I1 Good 3596.635
2 SI2 Good 4580.261
3 SI1 Good 3689.533
4 VS2 Good 4262.236
5 VS1 Good 3801.446
6 VVS2 Good 3079.108
7 VVS1 Good 2254.774
8 IF Good 4098.324
9 I1 Very Good 4078.226
10 SI2 Very Good 4988.688
11 SI1 Very Good 3932.391
12 VS2 Very Good 4215.760
13 VS1 Very Good 3805.353
14 VVS2 Very Good 3037.765
15 VVS1 Very Good 2459.441
16 IF Very Good 4396.216
17 I1 Premium 3947.332
18 SI2 Premium 5545.937
19 SI1 Premium 4455.269
20 VS2 Premium 4550.331
21 VS1 Premium 4485.462
22 VVS2 Premium 3795.123
23 VVS1 Premium 2831.206
24 IF Premium 3856.143
25 I1 Ideal 4335.726
26 SI2 Ideal 4755.953
27 SI1 Ideal 3752.118
28 VS2 Ideal 3284.550
29 VS1 Ideal 3489.744
30 VVS2 Ideal 3250.290
31 VVS1 Ideal 2468.129
32 IF Ideal 2272.913
#if you use View() RStudio opens a new tab with the data
diamonds %>%
filter(cut!='Fair') %>%
group_by(cut,clarity) %>%
summarise(mean_price=mean(price)) %>%
spread(key=cut,value=mean_price) %>%
gather(cut,price, Good:Ideal ) %>%
View()
some helpfull other tutorials
Have fun exploring your data!
LS0tDQp0aXRsZTogImRwbHlyIHR1dG9yaWFsIg0Kb3V0cHV0Og0KICAjaHRtbF9kb2N1bWVudDogZGVmYXVsdA0KICAjcGRmX2RvY3VtZW50OiBkZWZhdWx0DQogIG91dHB1dDogaHRtbF9ub3RlYm9vaw0KdXJsY29sb3I6IGJsdWUNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmBgYA0KDQpEcGx5ciBpcyBhIGdyYW1tYXIgZm9yIGRhdGEgbWFuaXB1bGF0aW9uLiBJdCBjb21iaW5lcyB2YXJpb3VzIHN0ZXBzIG9mIG1hbmlwdWxhdGlvbiBpbiBvbmUgY2h1bmsgb2YgY29kZSB3aXRob3V0IG5lc3RpbmcuIA0KDQpGb3IgbW9yZSBpbmZvcm1hdGlvbiBjaGVjayB0aGUgb2ZmaWNpYWwgW3dlYnNpZGVdKGh0dHA6Ly9kcGx5ci50aWR5dmVyc2Uub3JnLykuDQoNCiMjVGlkeXZlcnNlDQpUaGUgcGFja2FnZSBpcyBwYXJ0IG9mIHRoZSAidGlkeXZlcnMtVW5pdmVyc2UiLiANClRpZHl2ZXJzZSBpcyBhIGEgY29sbGVjdGlvbiBvZiBwYWNrYWdlcyB3aGljaCBzaGFyZSBhIGNvbW1vbiBwaGlsb3NvcGh5IG9mIGRhdGEgYW5hbHlzaXMgYW5kIFIgcHJvZ3JhbW1pbmcgYW5kIGFyZSBkZXNpZ25lZCB0byB3b3JrIHRvZ2V0aGVyIHNlYW1sZXNzbHkuDQoNCi0gZ2dwbG90MiwgZm9yIGRhdGEgdmlzdWFsaXNhdGlvbi4NCi0gZHBseXIsIGZvciBkYXRhIG1hbmlwdWxhdGlvbi4NCi0gdGlkeXIsIGZvciBkYXRhIHRpZHlpbmcuDQotIHJlYWRyLCBmb3IgZGF0YSBpbXBvcnQuDQotIHB1cnJyLCBmb3IgZnVuY3Rpb25hbCBwcm9ncmFtbWluZy4NCi0gdGliYmxlLCBmb3IgdGliYmxlcywgYSBtb2Rlcm4gcmUtaW1hZ2luaW5nIG9mIGRhdGEgZnJhbWVzLg0KDQpUaGV5IHNoYXJlIGNvbW1vbiBkYXRhIHJlcHJlc2VudGF0aW9uIGFuZCBBUEkgZGVzaWduIGFuZCB0aGUgdGlkeXZlcnNlIHBhY2thZ2UgYWxsb3dzIHlvdSB0byBpbnN0YWxsIGFuIHVwZGF0ZSB0aGVtIGFzIGEgc2V0ICh0aGV5IHVwZGF0ZSBmcmVxdWVudGx5KS4NCg0KDQojI0ludHJvZHVjdGlvbg0KVGhpcyB0dXRvcmlhbCB3aWxsIGludHJvZHVjZSB0aGUgYmFzaWMgY29tbWFuZHMgdG8gd29yayB3aXRoIGRwbHlyLg0KVGhpcyBbY2hlYXQgc2hlZXRdKGh0dHBzOi8vZ2l0aHViLmNvbS9yc3R1ZGlvL2NoZWF0c2hlZXRzL3Jhdy9tYXN0ZXIvc291cmNlL3BkZnMvZGF0YS10cmFuc2Zvcm1hdGlvbi1jaGVhdHNoZWV0LnBkZilieSBSLVN0dWRpbyBnaXZlcyBhIG92ZXJ2aWV3IG9mIG1vcmUgaGVscGZ1bCBjb21tYW5kcy4NCg0KVGhlIGZ1bmN0aW9ucyB3b3JrIGluIGEgInJlZ3VsYXIiIHdheSBhbmQgaW4gdGhlICU+JS12ZXJzaW9uLiANCg0KICArIGZ1bmN0aW9uKGRhdGEsLi4uLikNCg0Kb3INCg0KICArIGRhdGEgJT4lIGZ1bmN0aW9uKC4uLi4pLg0KDQpBcyB0aGUgYXV0aG9yIG9mIHRoaXMgdHV0b3JpYWwgbGlrZXMgdGhlICU+JSBhIGxvdCwgbW9zdCBmdW5jdGlvbnMgd2lsbCBiZSBzaG93biBpbiB0aGlzIHdheS4NClRoZSBzaG9ydGN1dCBmb3IgJyAlPiUgJyBpcyBzdHJnK2NvbW1hbmQrbSAgDQo8YnI+DQo8YnI+DQpMZXQncyBnZXQgc3RhcnRlZC4gRmlyc3QgeW91IGhhdmUgdG8gaW5zdGFsbCBhbmQgbG9hZCB0aGUgdGhlIHRpZHl2ZXJzZSBwYWNrYWdlIChvciBkcGx5ciBhbmQgZ2dwbG90MiBpZiB5b3Ugd2FudCB0byBkbyBpdCBzZXBhcmF0bHkpLiANCg0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KI3dlIHVzZSB0aGUgZGlhbW9uZHMtZGF0YSANCmBgYA0KDQoNCiMjc2VsZWN0KCkNCnNlbGVjdHMgYSBzZXQgb2YgY29sdW1ucw0KDQpgYGB7cn0NCg0KI0NsYXNzaWMgU3R5bGUNCnNlbGVjdChkaWFtb25kcywgY3V0LCBjb2xvcikgDQoNCmBgYA0KYGBge3J9DQojcGlwZS1zdHlsZQ0KZGlhbW9uZHMgJT4lIHNlbGVjdChjdXQsY29sb3IpIA0KYGBgDQoNCmBgYHtyfQ0KI2FkZGluZyB0aGUgY29tYW5kIGhlYWQgYXQgdGhlIGVuZCwgdG8gb25seSBzZWxlY3QgdGhlIGZpc3QgZml2ZSByb3dzDQpkaWFtb25kcyAlPiUgc2VsZWN0KGN1dCwgY29sb3IpICU+JSBoZWFkDQpgYGANCg0KYGBge3J9DQojc2VsZWN0IGFsbCByb3dzIHN0YXJpbmcgd2l0aCAiYyIgDQpkaWFtb25kcyAlPiUgc2VsZWN0KHN0YXJ0c193aXRoKCdjJykscHJpY2UpDQoNCiNBbHNvIHdvcmtzIHdpdGggZW5kc193aXRoKCksIGNvbnRhaW5zKCksIG1hdGNoZXMoKSwuLi4NCmBgYA0KDQojZmlsdGVyDQpZb3UgY2FuIGZpbHRlciBieSB0aGUgdmFsdWVzIG9mIG9uZSBvciBtb3JlIGNvbHVtbnMuDQoNCmBgYHtyfQ0KI2ZpbHRlciBieSBvbmUgY29sdW1uDQpkaWFtb25kcyAlPiUgZmlsdGVyKGNvbG9yPT0nRScpDQpkaWFtb25kcyAlPiUgZmlsdGVyKHByaWNlPjM1MCkNCmBgYA0KDQpgYGB7cn0NCiN0aGUgJWluJS1jb21tYW5kIGlzIHVzZWZ1bGwgdG8gZmlsdGVyIG9uIHNldmVyYWwgdmFsdWVzDQpkaWFtb25kcyAlPiUgZmlsdGVyKGNvbG9yICVpbiUgYygnRScsJ0YnKSkNCmBgYA0KDQpgYGB7cn0NCiNUaGUgISBpbiBmcm9udCBvZiB0aGUgY29tbWFuZCBjaGFuZ2VzIHRoZSBvcGVyYXRvciB0byAibm90IGluIg0KZGlhbW9uZHMgJT4lIGZpbHRlcighY29sb3IgJWluJSBjKCdFJywnRicpKSANCmBgYA0KDQpgYGB7cn0NCiNmaWx0ZXIgb24gbW9yZSB0aGFuIG9uZSBjb2x1bW4NCg0KI1RoZSAiQU5EIi1jb21iaW5hdGlvbiBjYW4gYmUgZG9uZSBpbiB0d28gd2F5cw0KZGlhbW9uZHMgJT4lIGZpbHRlcihjb2xvcj09J0UnICwgcHJpY2U+MzUwKSANCg0KI2RpYW1vbmRzICU+JSBmaWx0ZXIoY29sb3I9PSdFJyAmIHByaWNlPjM1MCkNCiNoYXMgdGhlIHNhbWUgb3V0cHV0DQoNCmBgYA0KDQpgYGB7cn0NCiNmaWx0ZXJpbmcgd2l0aCAiT1IiIG9uIHR3byBjb2x1bW5zDQpkaWFtb25kcyAlPiUgZmlsdGVyKGNvbG9yPT0nRScgfCBwcmljZT4zNTApDQoNCg0KYGBgDQoNCiNhcnJhbmdlIA0KcmVvcmRlciB0aGUgcm93cyBieSBvbmUgb3IgbW9yZSBjb2x1bW5zDQpgYGB7cn0NCiNyZW9yZGVyIGJ5IHRoZSBjb2x1bW4gImN1dCINCmRpYW1vbmRzICU+JSBhcnJhbmdlKGN1dCkNCmBgYA0KDQpgYGB7cn0NCg0KI3Jlb3JkZXIgYnkgdGhlIGNvbHVtbiBjdXQgYW5kIHByaWNlLiBUbyBvcmRlciBpbiBkZXNjZW5kaW5nIHdheSwgcHV0IHRoZSBjb2x1bW4gaW4gZGVzYygpDQpkaWFtb25kcyAlPiUgYXJyYW5nZShjdXQsZGVzYyhwcmljZSkpIA0KDQoNCmBgYA0KI211dGF0ZSANCnlvdSBjYW4gY3JlYXRlIG5ldyBjb2x1bW5zIHZpYSBtdXRhdGUNCg0KYGBge3J9DQojc2ltcGxlIG5ldyBjb2x1bW4NCmRpYW1vbmRzICU+JSBzZWxlY3QoY3V0LCBjb2xvciwgcHJpY2UpICU+JSANCiAgICAgICAgICAgICAgbXV0YXRlKHByaWNlX24gPSBwcmljZSoxMDAwKQ0KYGBgDQoNCmBgYHtyfQ0KDQojbmV3IGNvbHVtbiBiYXNlIG9uIGEgY2FzZS1zdGF0ZW1lbnQNCmRpYW1vbmRzICU+JSBzZWxlY3QoY3V0LCBjb2xvciwgcHJpY2UpICU+JSANCiAgICAgICAgICAgICAgbXV0YXRlKGN1dF9uID0gY2FzZV93aGVuKC4kY3V0PT0nSWRlYWwnIH4gJzEnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuJGN1dD09J1ByZW1pdW0nIH4gJzInLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuJGN1dD09J1ZlcnkgR29vZCcgfiAnMycsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC4kY3V0PT0nR29vZCcgfiAnNCcsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC4kY3V0PT0nRmFpcicgfiAnNScsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgfiAnOScpKQ0KI2lmIHlvdSBtaXNzIGEgdmFsdWUsIGl0IGlzIGdvaW5nIHRvIGJlIE5BDQojIHRoZSBUUlVFLXN0YXRlbWVudCBpc3Qga2luZCBvZiBsaWtlIGEgImVsc2UiLXN0YXRlbWVudA0KDQpgYGANCiNzdW1tYXJpc2UgDQoNCllvdSBjYW4gdXNlIGFsbCBraW5kIG9mIGZ1bmN0aW9ucyB0byBzdW1tYXJpemUgdGhlIGRhdGEgKHN1bSwgbWVhbiwgbWVkaWFuLCAuLi4uLikuIA0KDQpgYGB7cn0NCiNjYWxjdWxhdGluZyB0aGUgbWVhbiBvZiB0aGUgY29sdW1uIHByaWNlDQpkaWFtb25kcyAlPiUgc3VtbWFyaXNlKG1lYW4ocHJpY2UpKQ0KYGBgDQoNCmBgYHtyfQ0KDQojYWRkaW5nIHRoZSBudW1iZXIgZGlzdGluY3QgdmFsdWVzIG9mIHRoZSBjb2x1bW4gY3V0IGFuZCB0aGUgbnVtYmVyIG9mIHJvd3MgaW4gdGhlIGRhdGFmcmFtZQ0KZGlhbW9uZHMgJT4lIHN1bW1hcmlzZShtZWFuX3ByaWNlPW1lYW4ocHJpY2UpLA0KICAgICAgICAgICAgICAgICAgICAgICBudW1fY3V0PW5fZGlzdGluY3QoY3V0KSwNCiAgICAgICAgICAgICAgICAgICAgICAgbnVtX3Jvdz1uKCkpDQoNCmBgYA0KDQojZ3JvdXBfYnkgDQpncm91cF9ieSBidWlsZHMgZ3JvdXBzIGJ5IHRoZSBkZWZpbmVkIGNvbHVtbnMgYW5kIGRvZXMgdGhlIG9wZXJhdGlvbnMgd2l0aGluIHRob3NlIGdyb3Vwcy4NCkZvciB0aG9zZSBmYW1pbGlhciB3aXRoIHNxbCBpdCB3b3JrcyBwcmV0dHkgbXVjaCB0aGUgc2FtZSBhcyAiZ3JvdXAgYnkiDQoNCmBgYHtyfQ0KI0dyb3VwIGJ5IG9uZSBjb2x1bW4gYW5kIHRoZW4gc3VtbWFyaXNpbmcgd2l0aCB0aG9zZSBncm91cHMNCmRpYW1vbmRzICU+JSBncm91cF9ieShjdXQpICU+JSBzdW1tYXJpc2UobWVhbl9wcmljZT1tZWFuKHByaWNlKSkNCmBgYA0KDQojcGlwaW5nIGNvbWFuZHMgdG9nZXRoZXINClNvIGZhcmUgdGhlIGNvbW1hbmRzIG1vc3RseSBqdXN0IGxvb2tlZCBkaWZmZXJlbnQgdG8gInJlZ3VsYXIiIHItY29tbWFuZHMuIE1heWJlIGFyZSBtb3JlIHN0cmFpZ2h0IGZvcndhcmQuIEJ1dCB0byBleHBlcmllbmNlIChJTU8pIHRoZSByZWFsIGFkdmFudGFnZSBvZiB0aGUgZHBseXItcGFja2FnZSwgeW91IGhhdmUgdG8gc3RhcnQgcGlwaW5nIHRoZSBjb21tYW5kcy4gDQoNCmBgYHtyfQ0KDQojIFNlbGVjdCBhbGwgZGlhbW9uZHMgd2l0aCBhIGZhaXIgY3V0IGFuZCB0aGVuIGNhbHVjYXRlIHRoZSBtZWFuIHByaWNlIGRlcGVuZGluZyBvbiB0aGUgY2xhcml0eQ0KZGlhbW9uZHMgJT4lIGZpbHRlcihjdXQ9PSdGYWlyJykgJT4lIGdyb3VwX2J5KGNsYXJpdHkpICU+JSBzdW1tYXJpc2UobWVhbl9wcmljZT1tZWFuKHByaWNlKSkNCg0KDQpgYGANCg0KDQojIHNvbWUgbW9yZSBoZWxwZnVsbCBmdW5jdGlvbnMNCg0KIyNjYXN0IC8gbWVsdCAtPiBmdW5jdGlvbnMgb2YgdGhlIHJlc2hhcGluZzIgcGFja2FnZQ0KSWYgeW91IGRvbid0IGtub3cgdGhlIGZ1bmN0aW9ucywgY2hlY2sgdGhlbSBvdXQuIFRoZXkgcmVhbGx5IGhlbHAgdG8gcmVvcmdhbml6ZSBkYXRhLg0KPGJyPg0KaW4gdGhpcyBjb250ZXh0ICh0aWR5dmVycy11bml2ZXJzZSkgdGhleSBhcmUgY2FsbGVkIHNwcmVhZCBhbmQgZ2F0aGVyIGFuZCBhcmUgcGFydCBvZiB0aGUgcGFja2FnZSAidGlkeXIiDQpgYGB7cn0NCg0KI3NwcmVhZCB0aGUgcmVzdWx0IA0KDQpkaWFtb25kcyAlPiUgDQogIGZpbHRlcihjdXQhPSdGYWlyJykgJT4lIA0KICBncm91cF9ieShjdXQsY2xhcml0eSkgJT4lIA0KICBzdW1tYXJpc2UobWVhbl9wcmljZT1tZWFuKHByaWNlKSkgJT4lIA0KICBzcHJlYWQoa2V5PWN1dCx2YWx1ZT1tZWFuX3ByaWNlKQ0KYGBgDQoNCmBgYHtyfQ0KDQoNCiNyZXZlcnNpbmcgdGhlIHNwcmVhZCB3aXRoIGdhdGhlcg0KZGlhbW9uZHMgJT4lIA0KICBmaWx0ZXIoY3V0IT0nRmFpcicpICU+JSANCiAgZ3JvdXBfYnkoY3V0LGNsYXJpdHkpICU+JSANCiAgc3VtbWFyaXNlKG1lYW5fcHJpY2U9bWVhbihwcmljZSkpICU+JSANCiAgc3ByZWFkKGtleT1jdXQsdmFsdWU9bWVhbl9wcmljZSkgICU+JSANCiAgZ2F0aGVyKGN1dCxwcmljZSwgR29vZDpJZGVhbCApDQogIA0KYGBgDQoNCg0KIyNzaG93IHRoZSBlbnRpcmUgZGF0YS5mcmFtZQ0KZHBseXIgYWx3YXlzIHNob3dzIHRoZSBmaXJzdCAxMCByb3dzLCBidXQgc29tZXRpbWVzIHlvdSBqdXN0IHdhbnQgdG8gc2VlIHRoZW0gYWxsDQoNCmBgYHtyfQ0KI3RvIHNlZSB0aGUgcmVzdWx0IGluIHRoZSBjb25zb2xlLCB1c2UgcHJpbnQobj1ucm93KC4pKQ0KZGlhbW9uZHMgJT4lIA0KICBmaWx0ZXIoY3V0IT0nRmFpcicpICU+JSANCiAgZ3JvdXBfYnkoY3V0LGNsYXJpdHkpICU+JSANCiAgc3VtbWFyaXNlKG1lYW5fcHJpY2U9bWVhbihwcmljZSkpICU+JSANCiAgc3ByZWFkKGtleT1jdXQsdmFsdWU9bWVhbl9wcmljZSkgICU+JSANCiAgZ2F0aGVyKGN1dCxwcmljZSwgR29vZDpJZGVhbCApICU+JSANCiAgcHJpbnQobiA9IG5yb3coLikpDQpgYGANCg0KYGBge3J9DQoNCiNpZiB5b3UgdXNlIFZpZXcoKSBSU3R1ZGlvIG9wZW5zIGEgbmV3IHRhYiB3aXRoIHRoZSBkYXRhDQpkaWFtb25kcyAlPiUgDQogIGZpbHRlcihjdXQhPSdGYWlyJykgJT4lIA0KICBncm91cF9ieShjdXQsY2xhcml0eSkgJT4lIA0KICBzdW1tYXJpc2UobWVhbl9wcmljZT1tZWFuKHByaWNlKSkgJT4lIA0KICBzcHJlYWQoa2V5PWN1dCx2YWx1ZT1tZWFuX3ByaWNlKSAgJT4lIA0KICBnYXRoZXIoY3V0LHByaWNlLCBHb29kOklkZWFsICkgJT4lIA0KICBWaWV3KCkNCg0KDQpgYGANCg0KDQojI3NvbWUgaGVscGZ1bGwgb3RoZXIgdHV0b3JpYWxzDQoNCi0gW0RhdGEgTWFuaXB1bGF0aW9uIHdpdGggZHBseXJdKGh0dHBzOi8vd3d3LnItYmxvZ2dlcnMuY29tL2RhdGEtbWFuaXB1bGF0aW9uLXdpdGgtZHBseXIvKQ0KLSBbTGVzc2VyIGtub3duIGRwbHlyIHRyaWNrc10oaHR0cHM6Ly93d3cuci1ibG9nZ2Vycy5jb20vbGVzc2VyLWtub3duLWRwbHlyLXRyaWNrcy8pDQotIFtTb21lIHRyaWNrcyBvbiBkcGx5cjo6ZmlsdGVyXShodHRwczovL3NlYmFzdGlhbnNhdWVyLmdpdGh1Yi5pby9kcGx5cl9maWx0ZXIvKQ0KDQoNCg0KSGF2ZSBmdW4gZXhwbG9yaW5nIHlvdXIgZGF0YSENCg0KDQoNCg0KDQo=